#pragma once 
#include "data.hpp"
#include "httplib.h"
using namespace shy;

extern cloud::DataManager* _data;


namespace cloud 
{
  class Service
  {
    private:
      int _server_port;
      std::string _server_ip;
      std::string _download_prefix;
      httplib::Server _server;
    public:
      static void Upload(const httplib::Request &req, httplib::Response &rsp)
      {
        // post /upload 
        auto ret = req.has_file("file"); // judge it if there is a file update area 
        if (ret == false)
        {
          rsp.status = 400;
          return;
        }
        const auto& file = req.get_file_value("file");
        std::string back_dir = Config::GetInstance()->GetBackDir();
        std::string realpath = back_dir + FileUtil(file.filename).FileName();
        FileUtil fu(realpath);
        std::string content = file.content;
        fu.SetContent(content); // write data to file 
        BackupInfo info;
        info.NewBackupInfo(realpath);
        _data->Insert(info);

        rsp.status = 200;
        return ;
      }
      static std::string TimetoStr(time_t t)
      {
        std::string tmp = std::ctime(&t);
        return tmp;
      }
      static void ListShow(const httplib::Request &req , httplib::Response &rsp)
      {
        // 1. get all file backup info 
        std::vector<BackupInfo> arry;

        _data->GetAll(&arry);

        // 2. create html pages based on file data 
        std::stringstream ss;
        ss << "<html><head><title>Download</title></head>";
        ss << "<body><h1>Download</h1><table>";
        for (auto &a : arry)
        {
          ss << "<tr>"; 
          std::string filename = FileUtil(a.real_path).FileName();
          ss << "<td><a href ='" << a.url << "'>" << filename <<"</a></td>";
          ss << "<td align='right'>" << TimetoStr(a.mtime) << "</td>";
          ss << "<td align='right'>" << a.fsize / 1024 << "k</td>";
          ss << "</tr>";
        }
          ss << "</table></body></html>";
          rsp.body = ss.str();
          rsp.set_header("Content-Type" , "text/html");
          rsp.status = 200;
          return;
      }
      static std::string GetETag(const BackupInfo& info)
      {
        // etg : filename + fszie + mtime 
        FileUtil fu(info.real_path);
        std::string etag = fu.FileName();
        etag += "-";
        etag += std::to_string(info.fsize);
        etag += "-";
        etag += std::to_string(info.mtime);
        return etag;
      }
      static void Download(const httplib::Request &req, httplib::Response &rsp)
      {
        // 1.  get client Request path 
        // 2.  get backup info 
        BackupInfo info;
        _data->GetOneByURL(req.path , &info);
        // 3.  uncompress if compressed
        if (info.pack_flag == true)
        {
          FileUtil fu(info.pack_path); 
          fu.uncompress(info.real_path);
          // 4.  delete packup info 
          fu.Remove();
          info.pack_flag = false;
          _data->Update(info);
        }
        // 5.  read file info 
        FileUtil fu(info.real_path);
        fu.GetContent(rsp.body);
        bool retrans = false;

        std::string old_etag;
        if (req.has_header("If-Range"))
        {
          // if we hanve if-range and the old_etag equal etag 
          
          old_etag = req.get_header_value("If-Range");
          if (old_etag == GetETag(info))
          {
            retrans = true;
          }
        }
        if (retrans == false)
        {
          // 6.  set ETag accept-ranges 
          rsp.set_header("Accept-Ranges" , "bytes");
          rsp.set_header("Content-Type" , "application/octet-stream");
          rsp.set_header("ETag" , GetETag(info));
          rsp.status = 200;
        }
        else 
        { 
          // httplib has achieced resume
          fu.GetContent(rsp.body);
          rsp.set_header("Content-Type" , "application/octet-stream");
          rsp.set_header("Accept-Ranges" , "bytes");
          rsp.set_header("ETag" , GetETag(info));
          rsp.status = 206; // range resquest responce status code 206
        }
      }
    public:
      Service()
      {
        Config* config = Config::GetInstance();
        _server_port = config->GetSetverPort();
        _server_ip = config->GetServerIP();
        _download_prefix = config->GetDownloadPrefix();
      }

      bool RunModule()
      {
        _server.Post("/upload" , Upload);
        _server.Get("/listshow" , ListShow);
        _server.Get("/" , ListShow);
        std::string download_url = _download_prefix + "(.*)";
        _server.Get(download_url , Download);
        _server.listen("0.0.0.0" , _server_port);
        return true;
      }
  };
}
